전체 무료 공개모든 정보는 무료로 공개됩니다. AI 도구 구독·시크릿혜택 링크는 제휴마케팅(어필리에이트) 링크이며, 구매·구독 시 운영자에게 소정의 커미션이 지급될 수 있습니다. 구독·구매자에게 추가 부담은 없으며, 월 최소 수십만 원에서 많게는 수백만 원까지 드는 AI 구독료와 채널 운영비에 큰 힘이 됩니다. 앞으로도 양질의 정보와 다양한 혜택을 꾸준히 제공하겠습니다.
네프콘 채널 가기
⌂ 홈으로
🌙
main.py
PYTHON

2.신규인허가 및 폐업 사업장 조회 웹 애플리케이션

파이썬PYTHON

설명

공공데이터 API로 지방행정 인허가·폐업 사업장을 조회하는 Flask 웹 앱이에요. 시도/시군구와 기간을 선택하면 Folium 지도(마커 클러스터)와 표로 결과를 보여주고, 엑셀 다운로드와 다크모드도 지원해요.

태그

#Flask#공공데이터#지도#Folium#인허가조회#바이브코딩#데이터시각화#다크모드
프롬프트
파이썬 Flask 기반으로 "지방행정 인허가 신규/폐업 사업장 조회" 웹 애플리케이션을 만들어줘. 공공데이터 API(localdata.go.kr)에서 데이터를 받아와서, 지도와 표로 보여주는 서비스야. 아래 내용을 하나하나 꼼꼼하게 반영해줘.

========================================
1. 프로그램 개요 
========================================
이 프로그램은 공공데이터포털의 "지방행정 인허가 데이터" API를 활용해서, 사용자가 선택한 지역(시도/시군구)과 날짜 범위 안에서 새로 인허가를 받았거나 폐업한 사업장 정보를 조회하는 웹 앱이야.

조회 결과는 Folium 지도(마커 클러스터)와 HTML 표 두 가지 형태로 보여주고, 엑셀 파일로도 다운로드할 수 있게 해줘.

파일 하나(localdata_viewer.py)에 Flask 서버 코드와 HTML 템플릿을 전부 담아줘. HTML은 별도 파일이 아니라 파이썬 코드 안에 문자열(render_template_string)로 넣어줘.

========================================
2. 기능
========================================
2-1. 지역 선택

"local_codes.xlsx"라는 엑셀 파일에 시도명, 시군구명, 시군구(자치단체)코드가 들어있어. 이 파일을 프로그램 시작할 때 pandas로 읽어서(dtype=str) 시도 목록과 시군구 목록을 만들어줘.
화면에 "시도" 드롭다운과 "시군구" 드롭다운을 넣어줘.
시도 목록 맨 앞에는 "전체"를 넣어줘.
시도를 선택하면 해당 시도에 속한 시군구만 시군구 드롭다운에 나오게 해줘. 시군구도 맨 앞에 "전체"를 넣어줘.
시도를 "전체"로 선택하면 시군구는 "전체"만 선택 가능하게 해줘.

2-2. 날짜 선택

"시작일"과 "종료일" 날짜 입력(input type=date)을 넣어줘.
페이지 열릴 때 기본값은 시작일=전 달 24일, 종료일=오늘로 세팅해줘.
시작일의 min 속성도 전 달 24일로 걸어줘. 전 달 24일 이전 날짜로 조회하려고 하면 "전 달 24일 이전 기간은 조회할 수 없습니다." 경고를 띄워줘.

2-3. API 키 입력

화면 왼쪽 상단에 "API 키" 버튼을 고정(fixed) 배치해줘.
이 버튼을 누르면 모달 팝업이 뜨고, 거기에 API 키를 입력할 수 있게 해줘. input 타입은 password로 해줘.
입력한 키는 자바스크립트 변수(메모리)에만 저장하고, 서버 세션이나 로컬스토리지에는 저장하지 마.
모달 바깥 영역을 클릭하면 모달이 닫히게 해줘.
API 키 없이 조회하면 "API 키를 먼저 입력해주세요." 경고 후 모달을 자동으로 열어줘.

2-4. 데이터 조회 흐름

"조회" 버튼을 누르면 아래 순서로 진행해줘.

1단계) 서버의 /api/codes 엔드포인트를 호출해서, 선택한 시도/시군구에 해당하는 시군구(자치단체)코드 목록을 받아와.

2단계) 받아온 코드 목록을 하나씩 순회하면서, 각 코드마다 /api/data_single 엔드포인트를 호출해. 이때 코드, 시작일(YYYYMMDD), 종료일(YYYYMMDD), API키를 파라미터로 넘겨줘.

3단계) /api/data_single 내부에서는 공공데이터 API를 호출해. API URL은 "http://www.localdata.go.kr/platform/rest/TO0/openDataApi"이고, 파라미터는 authKey, localCode, pageSize(10000), bgnYmd, endYmd, lastModTsBgn, lastModTsEnd야. 응답은 XML인데, pandas의 read_xml(xpath=".//row")으로 파싱해줘.

4단계) 파싱한 데이터프레임의 칼럼명을 아래 매핑에 따라 한글로 바꿔줘. bplcNm→사업장명, opnSvcNm→개방서비스명, uptaeNm→업태구분명, dtlStateNm→상세영업상태명, rdnWhlAddr→도로명전체주소, rdnPostNo→도로명우편번호, siteTel→소재지전화, apvPermYmd→인허가일자, dcbYmd→폐업일자, x→좌표정보(X), y→좌표정보(Y) 이 칼럼들만 추출해서 JSON으로 클라이언트에 반환해줘.

5단계) 클라이언트에서는 모든 코드의 결과를 하나의 배열(ALL_ROWS)에 합쳐줘.

조회 진행 중에는 progress 바를 보여줘서 몇 퍼센트 진행됐는지 알 수 있게 해줘. (현재 처리한 코드 수 / 전체 코드 수)

조회가 끝나면 기본 정렬은 "인허가일자" 기준 내림차순(최신순)으로 해줘.

2-5. 결과표 (테이블)

"결과표" 탭에 조회 결과를 표로 보여줘.
표에 표시할 칼럼 순서: 사업장명, 개방서비스명, 업태구분명, 상세영업상태명, 도로명전체주소, 도로명우편번호, 소재지전화, 인허가일자, 폐업일자
좌표(X, Y) 칼럼은 표에는 보여주지 마. 지도용으로만 써.
표 헤더를 클릭하면 해당 칼럼 기준으로 정렬되게 해줘. 같은 헤더를 다시 클릭하면 오름차순↔내림차순 토글해줘. 현재 정렬 상태를 ▲ 또는 ▼ 화살표로 헤더에 표시해줘.
페이지네이션을 넣어줘. « First, ◀ Prev, 페이지번호/전체페이지, Next ▶, Last » 버튼과 한 페이지당 표시 건수(10, 20, 30, 50, 100)를 선택하는 드롭다운을 넣어줘. 페이지가 1페이지뿐이면 페이지네이션은 숨겨줘.
표 위에 "건수: N" 형태로 전체 건수를 보여줘.
표 영역은 높이 400px로 제한하고 스크롤 가능하게 해줘. 헤더는 sticky로 고정해줘.
셀이 너무 길면 말줄임표(text-overflow: ellipsis)로 처리하고, 마우스를 올리면 title 속성으로 전체 내용이 보이게 해줘. 셀 최대 너비는 200px로 해줘.

2-6. 지도

"지도" 탭에 Folium 지도를 보여줘.

지도 생성은 서버 쪽 /api/folium_map 엔드포인트에서 처리해줘. 클라이언트가 POST로 rows 배열과 dark(다크모드 여부)를 보내면, 서버에서 Folium 지도 HTML을 만들어서 반환해줘. 클라이언트는 받은 HTML을 map-area div에 innerHTML로 삽입해.

좌표 변환: 공공데이터의 X, Y는 EPSG:5174(중부원점) 좌표야. pyproj의 Transformer를 써서 EPSG:5174 → EPSG:4326(WGS84)으로 변환해줘. 좌표가 0,0이거나 변환 실패한 항목은 건너뛰어줘.

지도 중심점은 유효한 좌표들의 위도·경도 평균으로 잡아줘. 좌표가 하나도 없으면 서울시청(37.5665, 126.9780)으로 해줘.

기본 타일은 다크모드이면 "CartoDB dark_matter", 아니면 "CartoDB positron"으로 해줘. 추가로 "CartoDB positron"(밝은 지도), "CartoDB dark_matter"(어두운 지도), "OpenStreetMap"(OSM) 세 가지 타일 레이어를 넣어서 LayerControl에서 전환 가능하게 해줘.

마커는 업종(개방서비스명)별로 FeatureGroup을 나눠서 만들어줘. 각 FeatureGroup 안에 MarkerCluster를 넣어줘. MarkerCluster 옵션: spiderfyOnEveryZoom=True, spiderfyOnClick=True, showCoverageOnHover=False, zoomToBoundsOnClick=True

마커는 CircleMarker(반경 6, fill_opacity 0.8)를 써줘. 색상은 인허가일자 기준으로, 오늘로부터 30일 이내면 빨간색(#ff3333), 그 외에는 주황색(#ffa500)으로 해줘.

마커 클릭하면 팝업이 뜨게 해줘. 팝업에는 표에 보이는 칼럼들의 "칼럼명: 값" 형태를
로 연결해서 보여주되, 값이 비어있는 칼럼은 빼줘. 팝업 div 너비는 400px로 해줘.

MeasureControl(거리 측정, 위치 topleft, 단위 meters)을 넣어줘.

MiniMap(위치 bottomleft, toggle_display=True)을 넣어줘.

LayerControl(collapsed=False)을 넣어줘.

지도 옵션: control_scale=True, max_zoom=14, prefer_canvas=True

LayerControl이 확장됐을 때 max-height 300px에 overflow-y auto 스크롤 되게 CSS 넣어줘.

2-7. 엑셀 다운로드

조회 결과가 있으면 "다운로드" 버튼이 나타나게 해줘. 없으면 숨겨줘.
다운로드 버튼을 누르면 클라이언트의 ALL_ROWS를 서버 /api/download_excel에 POST로 보내줘.
서버에서는 pandas DataFrame으로 만든 뒤 xlsxwriter 엔진으로 엑셀 변환해서 BytesIO에 담아 send_file로 반환해줘.
파일명은 "localdata_YYYYMMDD_HHMMSS.xlsx" 형식으로 해줘.
클라이언트에서는 응답을 blob으로 받아서 Content-Disposition 헤더에서 파일명을 추출하고, 임시 a 태그를 만들어 다운로드 처리해줘.

========================================
3. 디자인
========================================

3-1. 전체 레이아웃

폰트는 구글 폰트 "Gothic A1" (400, 700 weight)을 쓰고, line-height 1.55로 해줘.
body와 html은 height 100%, margin 0으로 해줘.
전체를 #wrapper라는 div로 감싸고, flex column으로 높이 100% 채우게 해줘.
제목은 h1으로 "📍 신규인허가·폐업 사업장 조회", 가운데 정렬, 1.9rem으로 해줘.

3-2. 조건 입력 영역

#controls div에 시도, 시군구, 시작일, 종료일 label과 조회·다운로드 버튼을 넣어줘.
flex wrap, gap 1rem, justify-content center로 배치해줘.
각 label은 flex column, min-width 160px로 해줘.
select와 date input은 padding .45rem .6rem, 1rem 폰트, 1px solid #bbb 테두리, border-radius 4px로 해줘.
버튼 공통: padding .55rem 1rem, 1rem 폰트, border-radius 4px, 커서 pointer.
"조회" 버튼은 class="primary" (배경 #0066cc, 흰 글씨, hover시 #004fa3).
"다운로드" 버튼은 class="outline" (흰 배경, #0066cc 글씨, 1px solid #0066cc 테두리, hover시 배경 #e7f0ff).

3-3. 탭 구조

"지도"와 "결과표" 두 개의 탭 버튼을 .tabs div에 넣어줘.
탭 버튼: 배경 #f3f6fa, 1px solid #ddd 테두리(아래쪽 테두리 없음), padding .6rem 1rem. 활성 탭은 배경 #fff.
탭 콘텐츠(.tab-content)는 flex:1, flex-direction column, overflow hidden. 비활성 탭은 display:none(hidden 클래스).
지도 영역(#map-area)은 flex:1, min-height 300px. #map-area와 그 안의 .folium-map, .leaflet-container 모두 height:100%!important로 해줘.
기본 활성 탭은 "지도"로 해줘.
지도 탭을 활성화할 때 setTimeout으로 50ms 후 window resize 이벤트를 보내줘.

3-4. 다크모드

화면 오른쪽 상단에 🌙 이모지 버튼을 고정 배치해줘.
클릭하면 body에 'dark' 클래스를 토글하고, localStorage에 테마를 저장해줘.
페이지 로드 시 localStorage 값이 'dark'이거나, 저장값이 없고 OS가 다크모드 선호(prefers-color-scheme: dark)이면 다크모드 적용해줘.
다크모드 스타일: body.dark: 배경 #121212, 글자 #e0e0e0 h1: 글자 #fafafa select, input, button: 배경 #1e1e1e, 글자 #e0e0e0, 테두리 #555 table 글자 #e0e0e0, th 배경 #2b2b2b, 짝수행 배경 #1f1f1f, hover 배경 #2a2a2a 탭 버튼: 배경 #1e1e1e, 테두리 #555, 활성 배경 #121212 outline 버튼: 배경 #1e1e1e, 글자 #7ab4ff, 테두리 #3c5e80, hover 배경 #243142 API 키 버튼: 배경 #3c5e80, hover #2a4460 모달 배경: #1e1e1e, 글자 #e0e0e0, input 배경 #121212 취소 버튼: 배경 #2b2b2b, hover #3a3a3a

3-5. 모달 스타일

모달 오버레이: position fixed, 전체 화면, 배경 rgba(0,0,0,0.5), z-index 10000.
모달 콘텐츠: 흰 배경, margin 10% auto, padding 2rem, border-radius 8px, width 90%, max-width 400px, box-shadow 0 4px 6px rgba(0,0,0,0.1).
헤더 1.2rem 굵게, body에 input(width 100%, padding .5rem, 1rem 폰트), footer에 취소/확인 버튼 flex로 우측 정렬.

3-6. 기타 스타일

progress 바: width 100%, height 1.2rem, margin 1rem 0.
table: border-collapse, table-layout fixed, th/td 1px solid #ddd, padding .4rem .5rem, white-space nowrap.
짝수행 배경 #fafafa, hover 배경 #f1f7ff.
페이지네이션(#pager): flex wrap, gap .6rem, 가운데 정렬, margin 1rem.
========================================
4. 기술
========================================
백엔드: Python Flask
데이터 처리: pandas
HTTP 요청: requests (타임아웃 30초)
XML 파싱: pandas read_xml (StringIO 사용)
좌표 변환: pyproj Transformer (EPSG:5174 → EPSG:4326, always_xy=True)
지도: folium, folium.plugins의 MeasureControl, MarkerCluster, MiniMap
엑셀 변환: pandas ExcelWriter + xlsxwriter 엔진
프론트: HTML/CSS/JavaScript (프레임워크 없이 바닐라 JS)
HTML 템플릿은 textwrap.dedent로 인덴트 정리해서 파이썬 문자열에 담고, render_template_string으로 렌더링해줘.
Flask secret_key는 os.urandom(24)로 해줘.
시도/시군구 목록과 VISIBLE_COLS 배열은 json.dumps로 변환해서 템플릿에 {{ sido_json|safe }}, {{ sigungu_json|safe }}, {{ visible_cols|safe }}로 넘겨줘.
========================================
5. 추가 조건
========================================
파이썬 파일 인코딩은 utf-8로 해줘.
from future import annotations 넣어줘.
typing에서 List를 임포트해서 타입힌트에 사용해줘.
공공데이터 API 기본 URL은 상수 BASE_URL로, 페이지 크기는 PAGE_SIZE = 10000으로 정의해줘.
COLUMN_MAP 딕셔너리로 원본→한글 칼럼 매핑, VISIBLE_COLS 리스트로 표시할 칼럼 순서를 정의해줘.
/api/data_single에서 API 호출 실패 시 "401"이나 "인증"이 에러 메시지에 포함되면 "API 키가 유효하지 않습니다." 에러를, 그 외에는 빈 배열을 반환해줘.
NaN 값은 None으로 바꿔서 JSON 직렬화 문제가 없게 해줘.
코드 전체에 한글 주석을 상세하게 달아줘. 각 섹션, 함수, 주요 로직마다 설명을 넣어줘.
if name == "main": 에서 debug=True, use_reloader=False로 실행해줘.
모든 코드를 localdata_viewer.py 파일 하나에 담아줘.
========================================
※ 이 프롬프트는 바이브 코딩 + 수기 코딩으로 프로그램을 개발한 후 해당 프로그램을 구현하기 위해 리버스 프롬프트 엔지니어링 방식으로 만든 프롬프트입니다. 또한, AI의 랜덤 성향 특성상 위 프롬프트를 사용한다고 하더라도 동일 프로그램이 되지 않는다는 점 참고해 주세요.
main.py PYTHON